Lighter-View-Controller 轻量级视图控制器
视图控制器们(View Controllers)通常是最大的文件在我们的iOS工程中。而且 他们常常有很多不必要的代码。导致视图控制器往往是无法在其他项目中复用的。我们下面讲述的技巧可以让控制器们 更加轻量,有复用性,让不合理的代码出现在合理的位置
example project on GitHub
分离出数据协议和其他接口协议
- 一个很有用的技巧就是将视图控制器中的
UITableViewDataSource
这部分代码分离出来。独立写一个类来维护,如果你都是这么处理的话,那么你这些类是可以以后在其他项目中复用的。
- 这样做还有一个好处就是,我们可以独立测试这个类。
举个例子:在示例项目中,PhotosViewController
有这样几个方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| # pragma mark Pragma
- (Photo*)photoAtIndexPath:(NSIndexPath*)indexPath { return photos[(NSUInteger)indexPath.row]; }
- (NSInteger)tableView:(UITableView*)tableView numberOfRowsInSection:(NSInteger)section { return photos.count; }
- (UITableViewCell*)tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath { PhotoCell* cell = [tableView dequeueReusableCellWithIdentifier:PhotoCellIdentifier forIndexPath:indexPath]; Photo* photo = [self photoAtIndexPath:indexPath]; cell.label.text = photo.name; return cell; }
|
让我们试着把这些代码放到 我们自己的类上。我们用闭包来填充数据(或许协议更合适,这个取决于你)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| @implementation ArrayDataSource
- (id)itemAtIndexPath:(NSIndexPath*)indexPath { return items[(NSUInteger)indexPath.row]; }
- (NSInteger)tableView:(UITableView*)tableView numberOfRowsInSection:(NSInteger)section { return items.count; }
- (UITableViewCell*)tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath { id cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier forIndexPath:indexPath]; id item = [self itemAtIndexPath:indexPath]; configureCellBlock(cell,item); return cell; }
@end
|
这三个方法不再存在我们的视图控制器了,我们可以创建实例来承接table view的数据协议委托
1 2 3 4 5 6 7
| void (^configureCell)(PhotoCell*, Photo*) = ^(PhotoCell* cell, Photo* photo) { cell.label.text = photo.name; }; photosArrayDataSource = [[ArrayDataSource alloc] initWithItems:photos cellIdentifier:PhotoCellIdentifier configureCellBlock:configureCell]; self.tableView.dataSource = photosArrayDataSource;
|
而且,这种办法扩展其他接口协议也很方便。比如 另外的一个list数据协议是 UICollectionViewDataSource
由于一些需求实现,你们决定将 UITableView
替换为 UICollectionView
。实际上你的视图控制器不需要任何改动,你可以让你的类都支持这两个数据协议。
逻辑处理放在Model
这里还是一个例子,这些代码在view controller里,功能是返回一个列表,上面的数据是用户的活跃度
1 2 3 4 5 6 7
| - (void)loadPriorities { NSDate* now = [NSDate date]; NSString* formatString = @"startDate <= %@ AND endDate >= %@"; NSPredicate* predicate = [NSPredicate predicateWithFormat:formatString, now, now]; NSSet* priorities = [self.user.priorities filteredSetUsingPredicate:predicate]; self.priorities = [priorities allObjects]; }
|
当这些代码被放置到 User类的扩展中时,view controller就看起来比较清爽了。
1 2 3
| - (void)loadPriorities { self.priorities = [self.user currentPriorities]; }
|
User+Extensions.m:
1 2 3 4 5 6
| - (NSArray*)currentPriorities { NSDate* now = [NSDate date]; NSString* formatString = @"startDate <= %@ AND endDate >= %@"; NSPredicate* predicate = [NSPredicate predicateWithFormat:formatString, now, now]; return [[self.priorities filteredSetUsingPredicate:predicate] allObjects]; }
|
当然在实际项目中,有些代码不是那么轻松可以转移到Model当中的。所以我们要创建 “数据仓库”(Store Class)
创建仓库类
我们有些代码是从文件中加载数据并处理数据的,这些代码大概是这样的:
1 2 3 4 5 6 7 8 9 10 11 12 13
| - (void)readArchive { NSBundle* bundle = [NSBundle bundleForClass:[self class]]; NSURL *archiveURL = [bundle URLForResource:@"photodata" withExtension:@"bin"]; NSAssert(archiveURL != nil, @"Unable to find archive in bundle."); NSData *data = [NSData dataWithContentsOfURL:archiveURL options:0 error:NULL]; NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data]; _users = [unarchiver decodeObjectOfClass:[NSArray class] forKey:@"users"]; _photos = [unarchiver decodeObjectOfClass:[NSArray class] forKey:@"photos"]; [unarchiver finishDecoding]; }
|
其实 View Controller 没有必要关心数据是如何处理的,我们创建 数据仓库来处理这些。使这些代码分离出来,这样我们就可以复用这个仓库类了。
独立测试并且View Controller的代码量又减少了一些。这些数据仓库负责数据处理,持久化和数据库交互。我们也可以叫这些”仓库”为服务层或资料库
将网络服务逻辑转移到Model
这是一个类似的优化逻辑:不要把网络交互放在View Controller。把这些逻辑独立放置在其他类中,可以调用方法并设置回调处理。
这样做的好处是你可以格外处理所有你的数据和错误在这个类上。而不会让View Controller变的臃肿
结论
我们看到有很多技巧可以让View Controller更加精简。我们努力让这些技巧在实际开发中更加实用。
我们只有一个目的:写出可维护的代码。知道这些模式之后,我们有更加深刻的认识来讨论如何将笨重的View Controller精简优化。
扩展阅读